#include "Die.h"
#include "LexicalBlock.h"

#include <cstdio>

#include <libdwarf/dwarf.h>

namespace dwarf {

bool LexicalBlock::contains(Dwarf_Debug _debug, Dwarf_Die _die, const void *_pc) {
	Dwarf_Error error;
	Dwarf_Addr lowPc;
	int rtn = dwarf_lowpc(_die, &lowPc, &error);
	if (rtn == DW_DLV_ERROR) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return false;
	}
	else if (rtn == DW_DLV_OK) {
		void *highPc;
		rtn = Die::getHighPc(_debug, _die, (void *) lowPc, highPc);
		if (rtn == DW_DLV_ERROR) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		else if (rtn == DW_DLV_NO_ENTRY) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		return _pc >= (void *) lowPc && _pc < highPc;
	}
	RaiiPointer<Dwarf_Attribute> attribute(_debug);
	rtn = Die::getAttribute(_die, DW_AT_ranges, attribute);
	if (rtn == DW_DLV_ERROR) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		return false;
	}
	else if (rtn == DW_DLV_OK) {
		Dwarf_Off offset;
		rtn = dwarf_global_formref(attribute.getPointer(), &offset, &error);
		if (rtn == DW_DLV_ERROR) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		else if (rtn == DW_DLV_NO_ENTRY) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		Dwarf_Ranges *ranges;
		Dwarf_Signed rangeCount;
		Dwarf_Unsigned rangeByteCount;
		rtn = dwarf_get_ranges_a(_debug, offset, _die, &ranges, &rangeCount, &rangeByteCount, &error);
		if (rtn == DW_DLV_ERROR) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		else if (rtn == DW_DLV_NO_ENTRY) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			return false;
		}
		for (Dwarf_Signed i = 0; i < rangeCount; ++i) {
			if (ranges[i].dwr_type == DW_RANGES_END) {
				continue;
			}
			if (_pc >= (void *) ranges[i].dwr_addr1 && _pc < (void *) ranges[i].dwr_addr2) {
				dwarf_ranges_dealloc(_debug, ranges, rangeCount);
				return true;
			}
		}
		dwarf_ranges_dealloc(_debug, ranges, rangeCount);
	}
	return false;
}

LexicalBlock::LexicalBlock(bool &_rtn, Dwarf_Debug _debug, Dwarf_Die _die)
		: ranges_() {
	Dwarf_Error error;
	Dwarf_Addr lowPc;
	int rtn = dwarf_lowpc(_die, &lowPc, &error);
	if (rtn == DW_DLV_ERROR) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		_rtn = false;
		return;
	}
	else if (rtn == DW_DLV_OK) {
		void *highPc;
		rtn = Die::getHighPc(_debug, _die, (void *) lowPc, highPc);
		if (rtn == DW_DLV_ERROR) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			_rtn = false;
			return;
		}
		else if (rtn == DW_DLV_NO_ENTRY) {
			fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
			_rtn = false;
			return;
		}
		ranges_.push_back(std::pair<void *, void *>((void *) lowPc, highPc));
		_rtn = true;
		return;
	}
	Dwarf_Ranges *ranges;
	Dwarf_Signed rangeCount;
	Dwarf_Unsigned rangeByteCount;
	rtn = dwarf_get_ranges_a(_debug, 0, _die, &ranges, &rangeCount, &rangeByteCount, &error);
	if (rtn == DW_DLV_ERROR) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		_rtn = false;
		return;
	}
	else if (rtn == DW_DLV_NO_ENTRY) {
		fprintf(stderr, "[ERR] (%s, %d) %s\n", __FILE__, __LINE__, __FUNCTION__);
		_rtn = false;
		return;
	}
	for (Dwarf_Signed i = 0; i < rangeCount; ++i) {
		if (ranges[i].dwr_type == DW_RANGES_END) {
			continue;
		}
		ranges_.push_back(std::pair<void *, void *>((void *) ranges[i].dwr_addr1, (void *) ranges[i].dwr_addr2));
	}
	dwarf_ranges_dealloc(_debug, ranges, rangeCount);
	_rtn = true;
	return;
}

LexicalBlock::LexicalBlock(const LexicalBlock &_lexicalBlock)
		: ranges_(_lexicalBlock.ranges_) {
}

LexicalBlock::~LexicalBlock() {

}

const std::list<std::pair<void *, void *> > &LexicalBlock::getRanges() const {
	return ranges_;
}

}
